package com.zshan.clinic.admin.service.config;

import com.zshan.clinic.admin.constant.Constants;
import com.zshan.clinic.admin.entity.dto.ClinicEmpScheduleDTO;
import com.zshan.clinic.admin.entity.dto.CopyScheduleDTO;
import com.zshan.clinic.admin.entity.medical.schedule.TimeRange;
import com.zshan.clinic.admin.entity.vo.*;
import com.zshan.clinic.admin.mapper.config.SpecEmpScheduleMapper;
import com.zshan.clinic.admin.service.clinic.ClinicDeptService;
import com.zshan.clinic.admin.service.clinic.ClinicEmpService;
import com.zshan.clinic.admin.service.medical.ClinicRegisterService;
import com.zshan.clinic.admin.util.ShiftUtil;
import com.zshan.clinic.common.constant.CommonConstant;
import com.zshan.clinic.common.enums.YesOrNoEnum;
import com.zshan.clinic.common.exception.BusinessFailException;
import com.zshan.clinic.common.util.date.DateTimeUtils;
import com.zshan.clinic.common.util.page.PageInfo;
import com.zshan.clinic.common.util.page.PageUtils;
import com.zshan.clinic.common.util.string.StringUtil;
import com.zshan.clinic.common.util.uuid.IdBuilder;
import com.zshan.clinic.database.dao.ClinicDoctorDictMapper;
import com.zshan.clinic.database.dao.ClinicEmpScheduleDetailMapper;
import com.zshan.clinic.database.dao.ClinicEmpScheduleMapper;
import com.zshan.clinic.database.dao.ClinicEmpShiftMapper;
import com.zshan.clinic.database.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 排班设置
 */
@Service
@Slf4j
public class EmpScheduleService {

    /**
     * 班次表
     */
    @Autowired
    private ClinicEmpShiftMapper clinicEmpShiftMapper;

    /**
     * 排班表  张大夫今天上班
     */
    @Autowired
    private ClinicEmpScheduleMapper clinicEmpScheduleMapper;

    /**
     * 排班明细  下午14-18点
     */
    @Autowired
    private ClinicEmpScheduleDetailMapper clinicEmpScheduleDetailMapper;

    /**
     * 挂号配置 每人每周最多能爽约2次
     */
    @Autowired
    private ClinicDoctorDictMapper clinicDoctorDictMapper;

    @Autowired
    private ClinicEmpService clinicEmpService;

    @Autowired
    private ClinicDeptService clinicDeptService;

    @Autowired
    private SpecEmpScheduleMapper specEmpScheduleMapper;

    @Autowired
    private ClinicRegisterService registerService;

    /**
     * 工作台：获取员工排班
     * @param empId
     * @return
     */
    public List<ClinicEmpScheduleVo> getMySchedule(Long clinicId,Long empId){
        LocalDate startDate = LocalDate.now();
        LocalDate endDate = DateTimeUtils.addDays(startDate,CommonConstant.DEFAULT_7);
        return specEmpScheduleMapper.getMySchedule(clinicId,empId,startDate,endDate);
    }

    /**
     * 获取排班列表
     * @param startDate
     * @param endDate
     * @param empName
     * @param deptId
     * @param pageNo
     * @param pageSize
     * @return
     */
    public PageInfo<ClinicEmpSchedulePageVo> getEmpScheduleList(Long clinicId, LocalDate startDate, LocalDate endDate, String empName, Long deptId, Integer pageNo, Integer pageSize){
        if(startDate == null || endDate == null){
            return PageInfo.build();
        }
        Long totalCount = specEmpScheduleMapper.getEmpScheduleCount(clinicId,startDate,endDate,empName,deptId);
        if(StringUtil.isBlank(totalCount)){
            return PageInfo.build();
        }
        List<ClinicEmpSchedulePageVo> scheduleVoList = specEmpScheduleMapper.getEmpScheduleList(clinicId,startDate,endDate,empName,deptId,PageUtils.getOffset(pageNo,pageSize),pageSize);
        return new PageInfo<>(totalCount,scheduleVoList);
    }

    public ClinicEmpSchedule getClinicEmpScheduleByEmpAndWorkDate(Long clinicId,Long empId,LocalDate workDate){
        ClinicEmpScheduleExample example = new ClinicEmpScheduleExample();
        ClinicEmpScheduleExample.Criteria criteria = example.createCriteria();
        criteria.andIsDeleteEqualTo(YesOrNoEnum.NO.getValue());
        criteria.andClinicIdEqualTo(clinicId);
        criteria.andEmpIdEqualTo(empId);
        criteria.andWorkDateEqualTo(workDate);
        List<ClinicEmpSchedule> scheduleList = clinicEmpScheduleMapper.selectByExample(example);
        return CollectionUtils.isEmpty(scheduleList) ? null : scheduleList.get(0);
    }

    /**
     * 根据排班ID查询明细
     * @param scheduleId
     * @return
     */
    public List<ClinicEmpScheduleDetail> getClinicEmpScheduleDetailList(String scheduleId){
        ClinicEmpScheduleDetailExample example = new ClinicEmpScheduleDetailExample();
        ClinicEmpScheduleDetailExample.Criteria criteria = example.createCriteria();
        criteria.andIsDeleteEqualTo(YesOrNoEnum.NO.getValue());
        criteria.andScheduleIdEqualTo(scheduleId);
        List<ClinicEmpScheduleDetail> detailList = clinicEmpScheduleDetailMapper.selectByExample(example);
        return detailList;
    }

    /**
     * 维护排班
     * @param schedule
     */
    @Transactional(rollbackFor = Exception.class)
    public CancelRegisterVo saveSchedule(ClinicEmpScheduleDTO schedule) {
        validateScheduleDTO(schedule);
        CancelRegisterVo cancelRegisterVo = new CancelRegisterVo();
        cancelRegisterVo.setIsCancel(YesOrNoEnum.NO.getValue());
        ClinicEmpScheduleDTO dbSchedule = specEmpScheduleMapper.getScheduleDetail(
                schedule.getClinicId(),
                schedule.getEmpId(),
                schedule.getWorkDate()
        );
        if(dbSchedule == null){
            //添加排班
            addSchedule(schedule);
        }else {
            cancelRegisterVo = validateCancelReg(
                    dbSchedule.getClinicId(),
                    dbSchedule.getEmpId(),
                    dbSchedule.getWorkDate(),
                    dbSchedule.getDetailList(),
                    schedule.getDetailList());
            if(YesOrNoEnum.YES.getValue().equals(cancelRegisterVo.getIsCancel())){
                return cancelRegisterVo;
            }
            // 如果是已有排班，先删除旧明细
            if (!CollectionUtils.isEmpty(dbSchedule.getDetailList())) {
                deleteScheduleDetailsBatch(dbSchedule.getClinicId(),Collections.singletonList(dbSchedule.getScheduleId()), schedule.getCreateUserId(), schedule.getCreateBy());
            }
            //说明要删除全部的排班
            if(CollectionUtils.isEmpty(schedule.getDetailList())){
                //删除受到影响的排班
                deleteScheduleBatch(schedule.getClinicId(),Collections.singletonList(dbSchedule.getScheduleId()),schedule.getCreateUserId(),schedule.getCreateBy());
                return cancelRegisterVo;
            }
            schedule.setScheduleId(dbSchedule.getScheduleId());
        }
        // 添加明细
        addScheduleDetailsBatch(
                schedule.getClinicId(),
                schedule.getScheduleId(),
                schedule.getCreateUserId(),
                schedule.getCreateBy(),
                schedule.getDetailList()
        );
        return cancelRegisterVo;
    }


    /**
     * 判断退号逻辑
     * @param clinicId
     * @param doctorId
     * @param dbDetailList 数据库中的
     * @param detailList 要添加的
     */
    public CancelRegisterVo validateCancelReg(Long clinicId,
                                              Long doctorId,
                                              LocalDate workDate,
                                              List<ClinicEmpScheduleDetail> dbDetailList,
                                              List<ClinicEmpScheduleDetail> detailList) {
        CancelRegisterVo cancelRegisterVo = new CancelRegisterVo();
        cancelRegisterVo.setIsCancel(YesOrNoEnum.NO.getValue());
        if(CollectionUtils.isEmpty(dbDetailList)){
            //代表全是要添加的数据
            return cancelRegisterVo;
        }
        //校验删除的
        cancelRegisterVo = validateDeleteDetail(clinicId,doctorId,workDate,dbDetailList,detailList);
        if(YesOrNoEnum.YES.getValue().equals(cancelRegisterVo.getIsCancel())){
            return cancelRegisterVo;
        }
        //检验修改的
        return validateUpdateDetail(clinicId,doctorId,workDate,dbDetailList,detailList);
    }

    public CancelRegisterVo validateUpdateDetail(Long clinicId, Long doctorId, LocalDate workDate, List<ClinicEmpScheduleDetail> dbDetailList, List<ClinicEmpScheduleDetail> detailList) {
        CancelRegisterVo cancelRegisterVo = new CancelRegisterVo();
        cancelRegisterVo.setIsCancel(YesOrNoEnum.NO.getValue());
        // 收集“新数据中保留/编辑的旧记录ID”（排除新增：detailId == null）
        Set<Long> dbDetailIdSet = CollectionUtils.isEmpty(dbDetailList)
                ? Collections.emptySet()
                : dbDetailList.stream()
                .filter(Objects::nonNull)
                .map(ClinicEmpScheduleDetail::getDetailId)
                .filter(Objects::nonNull)     // 仅保留有ID的（表示旧记录被保留/更新）
                .collect(Collectors.toSet());
        if(CollectionUtils.isEmpty(dbDetailIdSet)){
            return cancelRegisterVo;
        }
        // 过滤出：在 DB 已存在 且 请假(isLeave==YES) 的明细
        List<ClinicEmpScheduleDetail> updateDetailList = detailList.stream()
                .filter(Objects::nonNull)
                .filter(d -> d.getDetailId() != null)
                .filter(d -> dbDetailIdSet.contains(d.getDetailId()) && YesOrNoEnum.YES.getValue().equals(d.getIsLeave()))
                .collect(Collectors.toList());
        if(CollectionUtils.isEmpty(updateDetailList)){
            return cancelRegisterVo;
        }
        List<TimeRange> timeRangeList = updateDetailList.stream()
                .map(detail -> {
                    TimeRange timeRange = new TimeRange();
                    timeRange.setStart(detail.getLeaveStartTime());
                    timeRange.setEnd(detail.getLeaveEndTime());
                    return timeRange;
                }).collect(Collectors.toList());
        if(CollectionUtils.isEmpty(timeRangeList)){
            return cancelRegisterVo;
        }
        // 4) 查询这些时间段内已挂号/预约的记录
        List<ClinicRegisterVo> cancelRegisterList = registerService.getCancelRegisterList(clinicId, doctorId, workDate, timeRangeList);
        if (!CollectionUtils.isEmpty(cancelRegisterList)) {
            cancelRegisterVo.setIsCancel(YesOrNoEnum.YES.getValue());
            cancelRegisterVo.setCancelList(cancelRegisterList);
        }
        return cancelRegisterVo;
    }

    /**
     * 检验让删除的
     * @param clinicId
     * @param doctorId
     * @param workDate
     * @param dbDetailList
     * @param detailList
     * @return
     */
    public CancelRegisterVo validateDeleteDetail(Long clinicId, Long doctorId, LocalDate workDate, List<ClinicEmpScheduleDetail> dbDetailList, List<ClinicEmpScheduleDetail> detailList) {
        CancelRegisterVo cancelRegisterVo = new CancelRegisterVo();
        cancelRegisterVo.setIsCancel(YesOrNoEnum.NO.getValue());
        // 收集“新数据中保留/编辑的旧记录ID”（排除新增：detailId == null）
        Set<Long> newDetailIdSet = CollectionUtils.isEmpty(detailList)
                ? Collections.emptySet()
                : detailList.stream()
                .filter(Objects::nonNull)
                .map(ClinicEmpScheduleDetail::getDetailId)
                .filter(Objects::nonNull)     // 仅保留有ID的（表示旧记录被保留/更新）
                .collect(Collectors.toSet());

        // 语义：如果 newDetailIdSet 为空，是否表示“全部旧记录被删除”？
        // 若是，则保留下面这段；若不是，则直接 return Collections.emptyList();
        List<ClinicEmpScheduleDetail> deletedList;
        if (newDetailIdSet.isEmpty()) {
            // 语义：前端没有带任何旧ID -> 视为把旧的全删
            deletedList = new ArrayList<>(dbDetailList);
        } else {
            // 旧 - 新 = 被删
            deletedList = dbDetailList.stream()
                    .filter(Objects::nonNull)
                    .filter(d -> d.getDetailId() != null)
                    .filter(d -> !newDetailIdSet.contains(d.getDetailId()))
                    .collect(Collectors.toList());
        }
        if(CollectionUtils.isEmpty(deletedList)){
            //没有删除的
            return cancelRegisterVo;
        }
        Set<Long> targetShiftIds = deletedList.stream()
                .map(ClinicEmpScheduleDetail::getShiftId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        // 2) 查有效班次
        List<ClinicEmpShift> shiftList = getEnabledShiftListByIds(clinicId, new ArrayList<>(targetShiftIds));
        if(CollectionUtils.isEmpty(shiftList)){
            throw new BusinessFailException("班次不存在");
        }
        // 3) 构造 timeRange 列表
        List<TimeRange> timeRangeList = shiftList.stream()
                .filter(Objects::nonNull)
                .map(shift -> new TimeRange(shift.getStartTime(), shift.getEndTime()))
                .collect(Collectors.toList());
        // 4) 查询这些时间段内已挂号/预约的记录
        List<ClinicRegisterVo> cancelRegisterList = registerService.getCancelRegisterList(clinicId, doctorId, workDate, timeRangeList);
        if (!CollectionUtils.isEmpty(cancelRegisterList)) {
            cancelRegisterVo.setIsCancel(YesOrNoEnum.YES.getValue());
            cancelRegisterVo.setCancelList(cancelRegisterList);
        }
        return cancelRegisterVo;
    }

    public void validateScheduleDTO(ClinicEmpScheduleDTO schedule) {
        validateSchedule(schedule);
        List<ClinicEmpScheduleDetail> detailList = schedule.getDetailList();
        if(CollectionUtils.isEmpty(detailList)){
            return;
        }
//        if(CollectionUtils.isEmpty(detailList)){
//            throw new BusinessFailException("请先添加班次");
//        }
        List<Long> deptIds = detailList.stream()
                .map(ClinicEmpScheduleDetail::getDeptId)
                .collect(Collectors.toList());
        List<Long> roomIds = detailList.stream()
                .map(ClinicEmpScheduleDetail::getRoomId)
                .collect(Collectors.toList());
        List<Long> shiftIds = detailList.stream()
                .map(ClinicEmpScheduleDetail::getShiftId)
                .collect(Collectors.toList());
        // 批量查询科室和诊室
        List<ClinicDept> deptList = clinicDeptService.getEnabledClinicDeptByIds(schedule.getClinicId(),deptIds);
        if(CollectionUtils.isEmpty(deptList)){
            throw new BusinessFailException("科室不存在");
        }
        List<ClinicDeptRoom> roomList = clinicDeptService.getEnabledClinicRoomByIds(schedule.getClinicId(),roomIds);
        if(CollectionUtils.isEmpty(roomList)){
            throw new BusinessFailException("诊室不存在");
        }
        List<ClinicEmpShift> shiftList = getEnabledShiftListByIds(schedule.getClinicId(),shiftIds);
        if(CollectionUtils.isEmpty(shiftList)){
            throw new BusinessFailException("班次不存在");
        }
        // 转成 Map 方便查找
        Map<Long, ClinicDept> deptMap = deptList.stream()
                .collect(Collectors.toMap(ClinicDept::getDeptId, Function.identity()));

        Map<Long, ClinicDeptRoom> roomMap = roomList.stream()
                .collect(Collectors.toMap(ClinicDeptRoom::getRoomId, Function.identity()));

        Map<Long, ClinicEmpShift> shiftMap = shiftList.stream()
                .collect(Collectors.toMap(ClinicEmpShift::getShiftId, Function.identity()));
        // 校验
        List<TimeRange> timeRangeList = new ArrayList<>();
        for(ClinicEmpScheduleDetail detail : detailList) {
            if(!deptMap.containsKey(detail.getDeptId())) {
                throw new BusinessFailException("科室不存在");
            }
            if(!roomMap.containsKey(detail.getRoomId())) {
                throw new BusinessFailException("诊室不存在");
            }
            ClinicEmpShift shift = shiftMap.get(detail.getShiftId());
            if(shift == null){
                throw new BusinessFailException("班次不存在");
            }
            if(StringUtil.isBlank(detail.getIsLeave())){
                detail.setIsLeave(YesOrNoEnum.NO.getValue());
            }
            TimeRange timeRange = new TimeRange();
            timeRange.setStart(shift.getStartTime());
            timeRange.setEnd(shift.getEndTime());
            timeRangeList.add(timeRange);
        }
        if(ShiftUtil.hasOverlap(timeRangeList)){
            throw new BusinessFailException("当前排班班次时间上有冲突，请重新设置排班信息！");
        }
    }

    public List<ClinicEmpShift> getEnabledShiftListByIds(Long clinicId,List<Long> shiftIds) {
        if(CollectionUtils.isEmpty(shiftIds)){
            return null;
        }
        ClinicEmpShiftExample example = new ClinicEmpShiftExample();
        ClinicEmpShiftExample.Criteria criteria = example.createCriteria();
        criteria.andIsDeleteEqualTo(YesOrNoEnum.NO.getValue());
        criteria.andIsEnabledEqualTo(YesOrNoEnum.YES.getValue());
        criteria.andShiftIdIn(shiftIds);
        criteria.andClinicIdEqualTo(clinicId);
        return clinicEmpShiftMapper.selectByExample(example);
    }


    /**
     * 复制排班
     */
    @Transactional(rollbackFor = Exception.class)
    public CancelRegisterVo copySchedule(CopyScheduleDTO copyScheduleDTO){
        validateCopyScheduleDTO(copyScheduleDTO);
        List<ClinicEmpScheduleDTO> clinicEmpScheduleDTOList = specEmpScheduleMapper.getFormScheduleList(copyScheduleDTO.getClinicId(),copyScheduleDTO.getDeptIdList(),copyScheduleDTO.getFromStart(),copyScheduleDTO.getFromEnd());
        if(CollectionUtils.isEmpty(clinicEmpScheduleDTOList)){
            throw new BusinessFailException("没有根据日期匹配到排班");
        }
        //检查是否有需要退号的患者
        CancelRegisterVo cancelRegisterVo = new CancelRegisterVo();
        cancelRegisterVo.setIsCancel(YesOrNoEnum.NO.getValue());
        List<ClinicEmpScheduleDTO> dbEmpScheduleDTOList = specEmpScheduleMapper.getFormScheduleList(copyScheduleDTO.getClinicId(),copyScheduleDTO.getDeptIdList(),copyScheduleDTO.getTargetStart(),copyScheduleDTO.getTargetEnd());
        if(!CollectionUtils.isEmpty(dbEmpScheduleDTOList)){
            throw new BusinessFailException("部门在该日期下已经存在排班，不允许复制！");
        }
        //获取复制的排班信息
        List<ClinicEmpScheduleDTO> copyScheduleList = generateCopyScheduleList(
                clinicEmpScheduleDTOList,copyScheduleDTO.getCreateUserId(),copyScheduleDTO.getCreateBy(),
                copyScheduleDTO.getTargetStart(),copyScheduleDTO.getTargetEnd(),
                copyScheduleDTO.getFromStart(),copyScheduleDTO.getFromEnd());
        if(CollectionUtils.isEmpty(copyScheduleList)){
            throw new BusinessFailException("没有根据日期匹配到排班");
        }
        //批量添加排班数据
        specEmpScheduleMapper.addScheduleBatch(copyScheduleList);
        //批量添加排班明细
        specEmpScheduleMapper.addScheduleDetailsBatch(extractAllDetails(copyScheduleList));
        return cancelRegisterVo;
    }

    public void validateCopyScheduleDTO(CopyScheduleDTO copyScheduleDTO) {
        List<ClinicDept> deptList = clinicDeptService.getEnabledClinicDeptByIds(copyScheduleDTO.getClinicId(),copyScheduleDTO.getDeptIdList());
        if(CollectionUtils.isEmpty(deptList)){
            throw new BusinessFailException("科室不存在");
        }
        if (copyScheduleDTO.getFromStart() == null) {
            throw new IllegalArgumentException("源开始日期不能为空");
        }
        if (copyScheduleDTO.getFromEnd() == null) {
            throw new IllegalArgumentException("源结束日期不能为空");
        }
        if (copyScheduleDTO.getTargetStart() == null) {
            throw new IllegalArgumentException("目标开始日期不能为空");
        }
        if (copyScheduleDTO.getTargetEnd() == null) {
            throw new IllegalArgumentException("目标结束日期不能为空");
        }
        // 校验日期区间合法性
        if (copyScheduleDTO.getFromStart().isAfter(copyScheduleDTO.getFromEnd())) {
            throw new BusinessFailException("源日期范围不合法：开始日期不能晚于结束日期");
        }
        if (copyScheduleDTO.getTargetStart().isAfter(copyScheduleDTO.getTargetEnd())) {
            throw new BusinessFailException("目标日期范围不合法：开始日期不能晚于结束日期");
        }
        // 新增校验：目标开始日期不能早于源结束日期的下一天
        if (copyScheduleDTO.getTargetStart().isBefore(copyScheduleDTO.getFromEnd().plusDays(1))) {
            throw new BusinessFailException("目标日期范围不合法：目标开始日期必须晚于源结束日期");
        }
    }

    /**
     * 将排班明细从多个 DTO 中统一提取
     * 并设置外键字段（如 scheduleId）由外部保存时生成后再回写
     */
    public static List<ClinicEmpScheduleDetail> extractAllDetails(List<ClinicEmpScheduleDTO> scheduleList) {
        return scheduleList.stream()
                .filter(dto -> dto.getDetailList() != null)
                .flatMap(dto -> dto.getDetailList().stream()
                        .peek(detail -> {
                            // 如果主表已经设置主键，可以在这里设置外键：
                            // detail.setScheduleId(dto.getId());
                            // 如果还没有，则留空，由批量插入后回填
                        }))
                .collect(Collectors.toList());
    }



    /**
     * 根据源排班数据复制到目标区间（循环对齐复制）
     *
     * 【核心逻辑】
     * - 将源区间（fromStart ~ fromEnd）的天数作为“模板天数”。
     * - 目标区间（targetStart ~ targetEnd）会依次循环对应模板天数：
     *   目标第 k 天 对应 源 (fromStart + (k % srcDays))。
     * - 如果对应的源日期当天没有任何排班数据，则这一天在目标不生成排班。
     *
     * 举例：
     * 源区间： 10月12日 ～ 10月13日 （2天）
     * 目标区间： 10月14日 ～ 10月16日 （3天）
     * 循环对应：
     *   目标 14日 ← 源 12日
     *   目标 15日 ← 源 13日
     *   目标 16日 ← 源 12日（因为 2 % 2 = 0，循环回到第一天）
     *
     * 若源 12日没排班，13日有排班
     * → 则目标 14日不生成，15日生成，16日不生成。
     */
    public static List<ClinicEmpScheduleDTO> generateCopyScheduleList(
            List<ClinicEmpScheduleDTO> sourceList,
            Long createUserId, String createBy,
            LocalDate targetStart, LocalDate targetEnd,
            LocalDate fromStart, LocalDate fromEnd) {

        List<ClinicEmpScheduleDTO> result = new ArrayList<>();
        if (fromStart == null || fromEnd == null || targetStart == null || targetEnd == null) {
            return result; // 任一时间为空直接返回空列表
        }

        // 计算源日期区间天数，例如 12~13 是 2 天
        long srcDays = ChronoUnit.DAYS.between(fromStart, fromEnd) + 1;
        if (srcDays <= 0) {
            return result;
        }

        // 计算目标日期区间天数，例如 14~16 是 3 天
        long tgtDays = ChronoUnit.DAYS.between(targetStart, targetEnd) + 1;
        if (tgtDays <= 0) {
            return result;
        }

        // 将源的排班按日期分组，便于快速查找某天有没有数据
        Map<LocalDate, List<ClinicEmpScheduleDTO>> byDate = sourceList.stream()
                .collect(Collectors.groupingBy(ClinicEmpScheduleDTO::getWorkDate));

        // 遍历目标区间的每一天
        for (int k = 0; k < tgtDays; k++) {
            // 循环对应到源区间的日期
            LocalDate srcDate = fromStart.plusDays(k % srcDays);
            // 当前目标日期
            LocalDate dstDate = targetStart.plusDays(k);

            // 取出源日期当天的排班数据（如果没有就是空列表）
            List<ClinicEmpScheduleDTO> daySchedules = byDate.getOrDefault(srcDate, Collections.emptyList());

            // 源这天没数据，就跳过，不在目标生成排班
            if (daySchedules.isEmpty()) {
                continue;
            }

            // 拷贝排班数据到目标
            for (ClinicEmpScheduleDTO src : daySchedules) {
                ClinicEmpScheduleDTO copy = deepCopySchedule(createUserId, createBy, src);
                copy.setWorkDate(dstDate);
                result.add(copy);
            }
        }

        return result;
    }



    /**
     * 深拷贝单条排班数据（含明细列表）
     *
     * @param source 源排班对象
     * @return 拷贝后的新排班对象（不包含 ID，适合插入使用）
     */
    private static ClinicEmpScheduleDTO deepCopySchedule(Long createUserId,String createBy,ClinicEmpScheduleDTO source) {
        ClinicEmpScheduleDTO copy = new ClinicEmpScheduleDTO();
        BeanUtils.copyProperties(source, copy); // 拷贝主排班字段
        // 生成新的 UUID 作为主键ID
        String newScheduleId = IdBuilder.getID();
        copy.setScheduleId(newScheduleId);
        copy.setIsDelete(YesOrNoEnum.NO.getValue());
        copy.setIsEnabled(YesOrNoEnum.YES.getValue());
        copy.setCreateUserId(createUserId);
        copy.setCreateBy(createBy);
        copy.setCreateTime(new Date());
        // 拷贝排班明细列表
        List<ClinicEmpScheduleDetail> copiedDetails = new ArrayList<>();
        if (!CollectionUtils.isEmpty(source.getDetailList())) {
            for (ClinicEmpScheduleDetail detail : source.getDetailList()) {
                ClinicEmpScheduleDetail detailCopy = new ClinicEmpScheduleDetail();
                BeanUtils.copyProperties(detail, detailCopy); // 拷贝明细字段
                detailCopy.setIsLeave(YesOrNoEnum.NO.getValue());
                detailCopy.setScheduleId(newScheduleId);
                detailCopy.setIsDelete(YesOrNoEnum.NO.getValue());
                detailCopy.setIsEnabled(YesOrNoEnum.YES.getValue());
                detailCopy.setCreateUserId(createUserId);
                detailCopy.setCreateBy(createBy);
                detailCopy.setCreateTime(new Date());
                copiedDetails.add(detailCopy);
            }
        }
        copy.setDetailList(copiedDetails);
        return copy;
    }

    /**
     * 批量添加
     * @param clinicId
     * @param scheduleId
     * @param createUserId
     * @param createBy
     * @param detailList
     */
    @Transactional(rollbackFor = Exception.class)
    public void addScheduleDetailsBatch(Long clinicId, String scheduleId, Long createUserId,String createBy,List<ClinicEmpScheduleDetail> detailList) {
        if(CollectionUtils.isEmpty(detailList)){
            return;
        }
        Date createTime = new Date();
        for(ClinicEmpScheduleDetail clinicEmpScheduleDetail : detailList){
            clinicEmpScheduleDetail.setScheduleId(scheduleId);
            clinicEmpScheduleDetail.setClinicId(clinicId);
            clinicEmpScheduleDetail.setIsEnabled(YesOrNoEnum.YES.getValue());
            clinicEmpScheduleDetail.setIsDelete(YesOrNoEnum.NO.getValue());
            clinicEmpScheduleDetail.setCreateUserId(createUserId);
            clinicEmpScheduleDetail.setCreateBy(createBy);
            clinicEmpScheduleDetail.setCreateTime(createTime);
        }
        specEmpScheduleMapper.addScheduleDetailsBatch(detailList);
    }

    /**
     * 删除旧的排班
     * @param clinicId
     * @param scheduleIdList
     * @param updateBy
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteScheduleBatch(Long clinicId,List<String> scheduleIdList, Long updateUserId, String updateBy) {
        ClinicEmpScheduleExample example = new ClinicEmpScheduleExample();
        ClinicEmpScheduleExample.Criteria criteria = example.createCriteria();
        criteria.andClinicIdEqualTo(clinicId);
        criteria.andScheduleIdIn(scheduleIdList);
        criteria.andIsDeleteEqualTo(YesOrNoEnum.NO.getValue());
        ClinicEmpSchedule clinicEmpSchedule = new ClinicEmpSchedule();
        clinicEmpSchedule.setIsDelete(YesOrNoEnum.YES.getValue());
        clinicEmpSchedule.setUpdateUserId(updateUserId);
        clinicEmpSchedule.setUpdateBy(updateBy);
        clinicEmpScheduleMapper.updateByExampleSelective(clinicEmpSchedule,example);
    }

    /**
     * 删除旧的排班
     * @param scheduleIdList
     * @param updateUserId
     * @param updateBy
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteScheduleDetailsBatch(Long clinicId,List<String> scheduleIdList, Long updateUserId, String updateBy) {
        ClinicEmpScheduleDetailExample example = new ClinicEmpScheduleDetailExample();
        ClinicEmpScheduleDetailExample.Criteria criteria = example.createCriteria();
        criteria.andClinicIdEqualTo(clinicId);
        criteria.andScheduleIdIn(scheduleIdList);
        criteria.andIsDeleteEqualTo(YesOrNoEnum.NO.getValue());
        ClinicEmpScheduleDetail clinicEmpScheduleDetail = new ClinicEmpScheduleDetail();
        clinicEmpScheduleDetail.setIsDelete(YesOrNoEnum.YES.getValue());
        clinicEmpScheduleDetail.setUpdateUserId(updateUserId);
        clinicEmpScheduleDetail.setUpdateBy(updateBy);
        clinicEmpScheduleDetailMapper.updateByExampleSelective(clinicEmpScheduleDetail,example);
    }


    public List<ClinicEmpShiftVo> getShiftList(Long clinicId){
        List<ClinicEmpShift> shiftList = getAllClinicEmpShift(clinicId);
        if(CollectionUtils.isEmpty(shiftList)){
            return new ArrayList<>();
        }
        // 将实体列表转换为DTO列表
        return shiftList.stream().map(shift -> {
            ClinicEmpShiftVo clinicEmpShiftVo = new ClinicEmpShiftVo();
            BeanUtils.copyProperties(shift, clinicEmpShiftVo);
            return clinicEmpShiftVo;
        }).collect(Collectors.toList());
    }

    /**
     * 分页查询医生挂号时间间隔字典列表
     * @param clinicId 诊所ID
     * @return 分页结果
     */
    public ClinicDoctorDictVo getDoctorDict(Long clinicId) {
        ClinicDoctorDict clinicDoctorDict = getDictById(clinicId);
        if(clinicDoctorDict == null){
            return ClinicDoctorDictVo.createDefault();
        }
        ClinicDoctorDictVo clinicDoctorDictVo = new ClinicDoctorDictVo();
        BeanUtils.copyProperties(clinicDoctorDict, clinicDoctorDictVo);
        return clinicDoctorDictVo;
    }

    /**
     * 保存医生挂号常量设置
     * @param clinicDoctorDict
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveDict(ClinicDoctorDict clinicDoctorDict) {
        validateDict(clinicDoctorDict);
        ClinicDoctorDict dbClinicDoctorDict = getDictById(clinicDoctorDict.getClinicId());
        if(dbClinicDoctorDict == null){
            //保存
            clinicDoctorDict.setIsEnabled(YesOrNoEnum.YES.getValue());
            clinicDoctorDict.setIsDelete(YesOrNoEnum.NO.getValue());
            clinicDoctorDict.setCreateTime(new Date());
            clinicDoctorDictMapper.insertSelective(clinicDoctorDict);
        }else {
            //修改
            dbClinicDoctorDict.setMaxNoShowCount(clinicDoctorDict.getMaxNoShowCount());
            dbClinicDoctorDict.setLatestRegistrationTime(clinicDoctorDict.getLatestRegistrationTime());
            dbClinicDoctorDict.setReleaseAheadDay(clinicDoctorDict.getReleaseAheadDay());
            dbClinicDoctorDict.setUpdateUserId(clinicDoctorDict.getUpdateUserId());
            dbClinicDoctorDict.setUpdateBy(clinicDoctorDict.getUpdateBy());
            dbClinicDoctorDict.setUpdateTime(new Date());
            clinicDoctorDictMapper.updateByPrimaryKeySelective(dbClinicDoctorDict);
        }
    }


    /**
     * 查询班次列表
     * @param shiftName
     * @param pageNo
     * @param pageSize
     * @return
     */
    public PageInfo<ClinicEmpShiftVo> getShiftList(Long clinicId,String shiftName, Integer pageNo, Integer pageSize){
        PageInfo<ClinicEmpShift> pageInfo = getShiftListByCondition(clinicId,shiftName,pageNo,pageSize);
        if(StringUtil.isBlank(pageInfo.getTotalCount())){
            return PageInfo.build();
        }
        List<ClinicEmpShift> shiftList = pageInfo.getList();
        // 将实体列表转换为DTO列表
        return new PageInfo<>(pageInfo.getTotalCount(),shiftList.stream().map(shift -> {
            ClinicEmpShiftVo clinicEmpShiftVo = new ClinicEmpShiftVo();
            BeanUtils.copyProperties(shift, clinicEmpShiftVo);
            return clinicEmpShiftVo;
        }).collect(Collectors.toList()));
    }

    /**
     * 获取所有的班次
     * @return
     */
    public List<ClinicEmpShift> getAllClinicEmpShift(Long clinicId) {
        ClinicEmpShiftExample example = new ClinicEmpShiftExample();
        ClinicEmpShiftExample.Criteria criteria = example.createCriteria();
        criteria.andClinicIdEqualTo(clinicId);
        criteria.andIsDeleteEqualTo(YesOrNoEnum.NO.getValue());
        example.setOrderByClause("create_time asc");
        return clinicEmpShiftMapper.selectByExample(example);
    }

    /**
     * 分页查询班次配置列表
     * @param shiftName 班次名称
     * @param pageNo 页码
     * @param pageSize 每页条数
     * @return 分页结果
     */
    public PageInfo<ClinicEmpShift> getShiftListByCondition(Long clinicId,String shiftName, Integer pageNo, Integer pageSize) {
        ClinicEmpShiftExample example = new ClinicEmpShiftExample();
        ClinicEmpShiftExample.Criteria criteria = example.createCriteria();
        if (StringUtil.isNotBlank(shiftName)) {
            criteria.andShiftNameLike("%" + shiftName.trim() + "%");
        }
        criteria.andClinicIdEqualTo(clinicId);
        criteria.andIsDeleteEqualTo(YesOrNoEnum.NO.getValue());
        Long totalCount = clinicEmpShiftMapper.countByExample(example);
        if (totalCount == null || totalCount == 0) {
            return PageInfo.build();
        }
        example.setOffset(PageUtils.getOffset(pageNo, pageSize));
        example.setLimit(pageSize);
        example.setOrderByClause("start_time asc");
        List<ClinicEmpShift> list = clinicEmpShiftMapper.selectByExample(example);
        return new PageInfo<>(totalCount, list);
    }

    /**
     * 添加班次配置
     * @param shift 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void addShift(ClinicEmpShift shift) {
        validateShift(shift);
        shift.setIsDelete(YesOrNoEnum.NO.getValue());
        shift.setIsEnabled(YesOrNoEnum.YES.getValue());
        shift.setCreateTime(new Date());
        clinicEmpShiftMapper.insertSelective(shift);
    }

    /**
     * 校验班次配置参数
     * @param shift 实体
     */
    public void validateShift(ClinicEmpShift shift) {
        shift.setShiftName(StringUtil.removeAllWhitespace(shift.getShiftName()));
        if (StringUtil.isBlank(shift.getShiftName())) {
            throw new BusinessFailException("班次名称不能为空");
        }
        if(shift.getShiftName().length() > Constants.SCHEDULE_SHIFT_LENGTH){
            throw new BusinessFailException("班次名称"+Constants.SCHEDULE_SHIFT_LENGTH+"个字以内！");
        }
        LocalTime startTime = shift.getStartTime();
        if(startTime == null){
            throw new BusinessFailException("开始时间不能为空");
        }
        LocalTime endTime = shift.getEndTime();
        if(endTime == null){
            throw new BusinessFailException("结束时间不能为空");
        }
        int between = startTime.compareTo(endTime);
        if(between > 0){
            throw new BusinessFailException("开始时间不能晚于结束时间");
        }
        // 你可以根据业务需求补充更多校验，如时间段、排序号等
    }

    /**
     * 根据ID查询班次
     * @param shiftId 主键
     * @return 实体
     */
    public ClinicEmpShift getShiftById(Long shiftId) {
        if (StringUtil.isBlank(shiftId)) {
            return null;
        }
        ClinicEmpShift shift = clinicEmpShiftMapper.selectByPrimaryKey(shiftId);
        if (shift == null || YesOrNoEnum.YES.getValue().equals(shift.getIsDelete())) {
            return null;
        }
        return shift;
    }


    /**
     * 修改班次配置
     * @param shift 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public CancelRegisterVo updateShift(ClinicEmpShift shift) {
        validateShift(shift);
        CancelRegisterVo cancelRegisterVo = new CancelRegisterVo();
        cancelRegisterVo.setIsCancel(YesOrNoEnum.NO.getValue());
        ClinicEmpShift dbShift = getShiftById(shift.getShiftId());
        if (dbShift == null) {
            throw new BusinessFailException("班次不存在");
        }
        List<TimeRange> rangeList = ShiftUtil.getReducedRanges(dbShift.getStartTime(),dbShift.getEndTime(),shift.getStartTime(),shift.getEndTime());
        if(!CollectionUtils.isEmpty(rangeList)){
            //如果班次的时间发生了变化
            List<ClinicRegisterVo> cancelRegisterList = registerService.getCancelRegisterList(dbShift.getClinicId(), null, null, rangeList);
            if(!CollectionUtils.isEmpty(cancelRegisterList)){
                cancelRegisterVo.setIsCancel(YesOrNoEnum.YES.getValue());
                cancelRegisterVo.setCancelList(cancelRegisterList);
                return cancelRegisterVo;
            }
        }
        shift.setUpdateTime(new Date());
        clinicEmpShiftMapper.updateByPrimaryKeySelective(shift);
        return cancelRegisterVo;
    }


    /**
     * 删除班次配置（逻辑删除）
     * @param shift 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public CancelRegisterVo deleteShift(ClinicEmpShift shift) {
        CancelRegisterVo cancelRegisterVo = new CancelRegisterVo();
        cancelRegisterVo.setIsCancel(YesOrNoEnum.NO.getValue());
        ClinicEmpShift dbShift = getShiftById(shift.getShiftId());
        if (dbShift == null) {
            throw new BusinessFailException("班次不存在");
        }
        //查询受到影响的排班
        List<String> scheduleIdList = specEmpScheduleMapper.getScheduleIdList(dbShift.getClinicId(),dbShift.getShiftId());
        if(!CollectionUtils.isEmpty(scheduleIdList)){
            List<TimeRange> timeRangeList = Collections.singletonList(new TimeRange(dbShift.getStartTime(), dbShift.getEndTime()));
            List<ClinicRegisterVo> cancelRegisterList = registerService.getCancelRegisterList(dbShift.getClinicId(), null, null, timeRangeList);
            if(!CollectionUtils.isEmpty(cancelRegisterList)){
                cancelRegisterVo.setIsCancel(YesOrNoEnum.YES.getValue());
                cancelRegisterVo.setCancelList(cancelRegisterList);
                return cancelRegisterVo;
            }
            //删除受到影响的排班
            deleteScheduleBatch(dbShift.getClinicId(),scheduleIdList,shift.getUpdateUserId(),shift.getUpdateBy());
            //删除排班
            deleteScheduleDetailsBatch(dbShift.getClinicId(),scheduleIdList,shift.getUpdateUserId(),shift.getUpdateBy());
        }
        //删除班次
        dbShift.setIsDelete(YesOrNoEnum.YES.getValue());
        dbShift.setUpdateUserId(shift.getUpdateUserId());
        dbShift.setUpdateBy(shift.getUpdateBy());
        dbShift.setUpdateTime(new Date());
        clinicEmpShiftMapper.updateByPrimaryKeySelective(dbShift);
        return cancelRegisterVo;
    }


    /**
     * 启用班次配置
     * @param shift 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void enableShift(ClinicEmpShift shift) {
        ClinicEmpShift dbShift = getShiftById(shift.getShiftId());
        if (dbShift == null) {
            throw new BusinessFailException("班次不存在");
        }
        if (YesOrNoEnum.YES.getValue().equals(dbShift.getIsEnabled())) {
            return;
        }
        dbShift.setIsEnabled(YesOrNoEnum.YES.getValue());
        dbShift.setUpdateUserId(shift.getUpdateUserId());
        dbShift.setUpdateBy(shift.getUpdateBy());
        dbShift.setUpdateTime(new Date());
        clinicEmpShiftMapper.updateByPrimaryKeySelective(dbShift);
    }

    /**
     * 停用班次配置
     * @param shift 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void disableShift(ClinicEmpShift shift) {
        ClinicEmpShift dbShift = getShiftById(shift.getShiftId());
        if (dbShift == null) {
            throw new BusinessFailException("班次不存在");
        }
        if (YesOrNoEnum.NO.getValue().equals(dbShift.getIsEnabled())) {
            return;
        }
        dbShift.setIsEnabled(YesOrNoEnum.NO.getValue());
        dbShift.setUpdateUserId(shift.getUpdateUserId());
        dbShift.setUpdateBy(shift.getUpdateBy());
        dbShift.setUpdateTime(new Date());
        clinicEmpShiftMapper.updateByPrimaryKeySelective(dbShift);
    }


    /**
     * 分页查询员工排班列表
     * @param empId 员工ID
     * @param pageNo 页码
     * @param pageSize 每页条数
     * @return 分页结果
     */
    public PageInfo<ClinicEmpSchedule> getScheduleListByCondition(Long empId, Integer pageNo, Integer pageSize) {
        ClinicEmpScheduleExample example = new ClinicEmpScheduleExample();
        ClinicEmpScheduleExample.Criteria criteria = example.createCriteria();

        if (empId != null) {
            criteria.andEmpIdEqualTo(empId);
        }
        criteria.andIsDeleteEqualTo(YesOrNoEnum.NO.getValue());

        Long totalCount = clinicEmpScheduleMapper.countByExample(example);
        if (totalCount == null || totalCount == 0) {
            return PageInfo.build();
        }
        example.setOffset(PageUtils.getOffset(pageNo, pageSize));
        example.setLimit(pageSize);
        example.setOrderByClause("create_time desc");
        List<ClinicEmpSchedule> list = clinicEmpScheduleMapper.selectByExample(example);
        return new PageInfo<>(totalCount, list);
    }

    /**
     * 添加员工排班
     * @param schedule 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void addSchedule(ClinicEmpSchedule schedule) {
        schedule.setScheduleId(IdBuilder.getID());
        schedule.setIsDelete(YesOrNoEnum.NO.getValue());
        schedule.setIsEnabled(YesOrNoEnum.YES.getValue());
        schedule.setCreateTime(new Date());
        clinicEmpScheduleMapper.insertSelective(schedule);
    }


    /**
     * 校验排班参数
     * @param schedule 实体
     */
    public void validateSchedule(ClinicEmpSchedule schedule) {
        //校验 empId、排班日期等是否非空
        if(schedule.getWorkDate() == null){
            throw new BusinessFailException("排班日期不能为空");
        }
        ClinicEmp clinicEmp = clinicEmpService.getEnabledEmpById(schedule.getEmpId());
        if(clinicEmp == null){
            throw new BusinessFailException("员工不存在");
        }
        if(!clinicEmp.getClinicId().equals(schedule.getClinicId())){
            throw new BusinessFailException("员工不存在");
        }
    }

    /**
     * 根据ID查询排班记录
     * @param scheduleId 主键
     * @return 实体
     */
    public ClinicEmpSchedule getScheduleById(String scheduleId) {
        if (StringUtil.isBlank(scheduleId)) {
            return null;
        }
        ClinicEmpSchedule schedule = clinicEmpScheduleMapper.selectByPrimaryKey(scheduleId);
        if (schedule == null || YesOrNoEnum.YES.getValue().equals(schedule.getIsDelete())) {
            return null;
        }
        return schedule;
    }

    /**
     * 修改员工排班
     * @param schedule 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateSchedule(ClinicEmpSchedule schedule) {
        validateSchedule(schedule);
        ClinicEmpSchedule dbSchedule = getScheduleById(schedule.getScheduleId());
        if (dbSchedule == null) {
            throw new BusinessFailException("排班记录不存在");
        }
        schedule.setUpdateTime(new Date());
        clinicEmpScheduleMapper.updateByPrimaryKeySelective(schedule);
    }

    /**
     * 删除员工排班（逻辑删除）
     * @param schedule 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteSchedule(ClinicEmpSchedule schedule) {
        ClinicEmpSchedule dbSchedule = getScheduleById(schedule.getScheduleId());
        if (dbSchedule == null) {
            throw new BusinessFailException("排班记录不存在");
        }
        dbSchedule.setIsDelete(YesOrNoEnum.YES.getValue());
        dbSchedule.setUpdateUserId(schedule.getUpdateUserId());
        dbSchedule.setUpdateBy(schedule.getUpdateBy());
        dbSchedule.setUpdateTime(new Date());
        clinicEmpScheduleMapper.updateByPrimaryKeySelective(dbSchedule);
    }

    /**
     * 启用员工排班
     * @param schedule 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void enableSchedule(ClinicEmpSchedule schedule) {
        ClinicEmpSchedule dbSchedule = getScheduleById(schedule.getScheduleId());
        if (dbSchedule == null) {
            throw new BusinessFailException("排班记录不存在");
        }
        if (YesOrNoEnum.YES.getValue().equals(dbSchedule.getIsEnabled())) {
            return;
        }
        dbSchedule.setIsEnabled(YesOrNoEnum.YES.getValue());
        dbSchedule.setUpdateUserId(schedule.getUpdateUserId());
        dbSchedule.setUpdateBy(schedule.getUpdateBy());
        dbSchedule.setUpdateTime(new Date());
        clinicEmpScheduleMapper.updateByPrimaryKeySelective(dbSchedule);
    }

    /**
     * 停用员工排班
     * @param schedule 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void disableSchedule(ClinicEmpSchedule schedule) {
        ClinicEmpSchedule dbSchedule = getScheduleById(schedule.getScheduleId());
        if (dbSchedule == null) {
            throw new BusinessFailException("排班记录不存在");
        }
        if (YesOrNoEnum.NO.getValue().equals(dbSchedule.getIsEnabled())) {
            return;
        }
        dbSchedule.setIsEnabled(YesOrNoEnum.NO.getValue());
        dbSchedule.setUpdateUserId(schedule.getUpdateUserId());
        dbSchedule.setUpdateBy(schedule.getUpdateBy());
        dbSchedule.setUpdateTime(new Date());
        clinicEmpScheduleMapper.updateByPrimaryKeySelective(dbSchedule);
    }


    /**
     * 分页查询员工排班明细
     * @param scheduleId 排班主表ID
     * @param pageNo 页码
     * @param pageSize 每页条数
     * @return 分页结果
     */
    public PageInfo<ClinicEmpScheduleDetail> getDetailListByCondition(String scheduleId, Integer pageNo, Integer pageSize) {
        ClinicEmpScheduleDetailExample example = new ClinicEmpScheduleDetailExample();
        ClinicEmpScheduleDetailExample.Criteria criteria = example.createCriteria();
        if (scheduleId != null) {
            criteria.andScheduleIdEqualTo(scheduleId);
        }
        criteria.andIsDeleteEqualTo(YesOrNoEnum.NO.getValue());
        Long totalCount = clinicEmpScheduleDetailMapper.countByExample(example);
        if (totalCount == null || totalCount == 0) {
            return PageInfo.build();
        }
        example.setOffset(PageUtils.getOffset(pageNo, pageSize));
        example.setLimit(pageSize);
        example.setOrderByClause("create_time desc");
        List<ClinicEmpScheduleDetail> list = clinicEmpScheduleDetailMapper.selectByExample(example);
        return new PageInfo<>(totalCount, list);
    }

    /**
     * 添加排班明细
     * @param detail 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void addDetail(ClinicEmpScheduleDetail detail) {
        validateDetail(detail);
        detail.setIsDelete(YesOrNoEnum.NO.getValue());
        detail.setIsEnabled(YesOrNoEnum.YES.getValue());
        detail.setCreateTime(new Date());
        clinicEmpScheduleDetailMapper.insertSelective(detail);
    }

    /**
     * 校验明细参数
     * @param detail 实体
     */
    public void validateDetail(ClinicEmpScheduleDetail detail) {
        //校验 scheduleId、时间段等字段是否合理
        ClinicDept clinicDept = clinicDeptService.getEnabledClinicDeptById(detail.getDeptId());
        if(clinicDept == null){
            throw new BusinessFailException("科室不存在");
        }
        ClinicDeptRoom clinicDeptRoom = clinicDeptService.getEnabledClinicRoomById(detail.getRoomId());
        if(clinicDeptRoom == null){
            throw new BusinessFailException("诊室不存在");
        }
        ClinicEmpShift clinicEmpShift = getShiftById(detail.getShiftId());
        if(clinicEmpShift == null){
            throw new BusinessFailException("排班不存在");
        }
    }

    /**
     * 根据ID获取排班明细
     * @param detailId 明细主键
     * @return 实体
     */
    public ClinicEmpScheduleDetail getDetailById(Long detailId) {
        if (detailId == null) {
            return null;
        }
        ClinicEmpScheduleDetail detail = clinicEmpScheduleDetailMapper.selectByPrimaryKey(detailId);
        if (detail == null || YesOrNoEnum.YES.getValue().equals(detail.getIsDelete())) {
            return null;
        }
        return detail;
    }

    /**
     * 修改排班明细
     * @param detail 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateDetail(ClinicEmpScheduleDetail detail) {
        validateDetail(detail);
        ClinicEmpScheduleDetail dbDetail = getDetailById(detail.getDetailId());
        if (dbDetail == null) {
            throw new BusinessFailException("排班明细不存在");
        }
        detail.setUpdateTime(new Date());
        clinicEmpScheduleDetailMapper.updateByPrimaryKeySelective(detail);
    }

    /**
     * 删除排班明细（逻辑删除）
     * @param detail 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteDetail(ClinicEmpScheduleDetail detail) {
        ClinicEmpScheduleDetail dbDetail = getDetailById(detail.getDetailId());
        if (dbDetail == null) {
            throw new BusinessFailException("排班明细不存在");
        }
        dbDetail.setIsDelete(YesOrNoEnum.YES.getValue());
        dbDetail.setUpdateUserId(detail.getUpdateUserId());
        dbDetail.setUpdateBy(detail.getUpdateBy());
        dbDetail.setUpdateTime(new Date());
        clinicEmpScheduleDetailMapper.updateByPrimaryKeySelective(dbDetail);
    }

    /**
     * 启用排班明细
     * @param detail 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void enableDetail(ClinicEmpScheduleDetail detail) {
        ClinicEmpScheduleDetail dbDetail = getDetailById(detail.getDetailId());
        if (dbDetail == null) {
            throw new BusinessFailException("排班明细不存在");
        }
        if (YesOrNoEnum.YES.getValue().equals(dbDetail.getIsEnabled())) {
            return;
        }
        dbDetail.setIsEnabled(YesOrNoEnum.YES.getValue());
        dbDetail.setUpdateUserId(detail.getUpdateUserId());
        dbDetail.setUpdateBy(detail.getUpdateBy());
        dbDetail.setUpdateTime(new Date());
        clinicEmpScheduleDetailMapper.updateByPrimaryKeySelective(dbDetail);
    }

    /**
     * 停用排班明细
     * @param detail 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void disableDetail(ClinicEmpScheduleDetail detail) {
        ClinicEmpScheduleDetail dbDetail = getDetailById(detail.getDetailId());
        if (dbDetail == null) {
            throw new BusinessFailException("排班明细不存在");
        }
        if (YesOrNoEnum.NO.getValue().equals(dbDetail.getIsEnabled())) {
            return;
        }
        dbDetail.setIsEnabled(YesOrNoEnum.NO.getValue());
        dbDetail.setUpdateUserId(detail.getUpdateUserId());
        dbDetail.setUpdateBy(detail.getUpdateBy());
        dbDetail.setUpdateTime(new Date());
        clinicEmpScheduleDetailMapper.updateByPrimaryKeySelective(dbDetail);
    }


    /**
     * 分页查询医生挂号时间间隔字典列表
     * @param clinicId 诊所ID
     * @param pageNo 页码
     * @param pageSize 每页条数
     * @return 分页结果
     */
    public PageInfo<ClinicDoctorDict> getDoctorDictListByCondition(Long clinicId, Integer pageNo, Integer pageSize) {
        ClinicDoctorDictExample example = new ClinicDoctorDictExample();
        ClinicDoctorDictExample.Criteria criteria = example.createCriteria();
        if (clinicId != null) {
            criteria.andClinicIdEqualTo(clinicId);
        }
        criteria.andIsDeleteEqualTo(YesOrNoEnum.NO.getValue());
        Long totalCount = clinicDoctorDictMapper.countByExample(example);
        if (totalCount == null || totalCount == 0) {
            return PageInfo.build();
        }
        example.setOffset(PageUtils.getOffset(pageNo, pageSize));
        example.setLimit(pageSize);
        example.setOrderByClause("create_time desc");
        List<ClinicDoctorDict> list = clinicDoctorDictMapper.selectByExample(example);
        return new PageInfo<>(totalCount, list);
    }

    /**
     * 添加医生挂号时间间隔字典
     * @param clinicDoctorDict 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void addDict(ClinicDoctorDict clinicDoctorDict) {
        validateDict(clinicDoctorDict);
        clinicDoctorDict.setIsDelete(YesOrNoEnum.NO.getValue());
        clinicDoctorDict.setIsEnabled(YesOrNoEnum.YES.getValue());
        clinicDoctorDict.setCreateTime(new Date());
        clinicDoctorDictMapper.insertSelective(clinicDoctorDict);
    }

    /**
     * 校验实体参数
     * @param clinicDoctorDict 实体
     */
    public void validateDict(ClinicDoctorDict clinicDoctorDict) {
        if (clinicDoctorDict.getMaxNoShowCount() == null || clinicDoctorDict.getMaxNoShowCount() <= CommonConstant.DEFAULT_0) {
            throw new BusinessFailException("最大爽约次数必须大于"+CommonConstant.DEFAULT_0+"次");
        }
        if (clinicDoctorDict.getLatestRegistrationTime() == null || clinicDoctorDict.getLatestRegistrationTime() <= CommonConstant.DEFAULT_0) {
            throw new BusinessFailException("最晚取号时间必须大于"+CommonConstant.DEFAULT_0+"分钟");
        }
        if (clinicDoctorDict.getReleaseAheadDay() == null || clinicDoctorDict.getReleaseAheadDay() <= CommonConstant.DEFAULT_0) {
            throw new BusinessFailException("预约范围必须大于"+CommonConstant.DEFAULT_0+"天");
        }
    }

    /**
     * 根据ID获取字典详情
     * @param clinicId 主键
     * @return 实体
     */
    public ClinicDoctorDict getDictById(Long clinicId) {
        if (StringUtil.isBlank(clinicId)) {
            return null;
        }
        ClinicDoctorDict dict = clinicDoctorDictMapper.selectByPrimaryKey(clinicId);
        if (dict == null || YesOrNoEnum.YES.getValue().equals(dict.getIsDelete())) {
            return null;
        }
        return dict;
    }

    /**
     * 修改医生挂号时间间隔字典
     * @param dict 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateDict(ClinicDoctorDict dict) {
        validateDict(dict);
        ClinicDoctorDict dbDict = getDictById(dict.getClinicId());
        if (dbDict == null) {
            throw new BusinessFailException("设置不存在");
        }
        dict.setUpdateTime(new Date());
        clinicDoctorDictMapper.updateByPrimaryKeySelective(dict);
    }

    /**
     * 删除医生挂号时间间隔字典（逻辑删除）
     * @param dict 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteDict(ClinicDoctorDict dict) {
        ClinicDoctorDict dbDict = getDictById(dict.getClinicId());
        if (dbDict == null) {
            throw new BusinessFailException("设置不存在");
        }
        dbDict.setIsDelete(YesOrNoEnum.YES.getValue());
        dbDict.setUpdateUserId(dict.getUpdateUserId());
        dbDict.setUpdateBy(dict.getUpdateBy());
        dbDict.setUpdateTime(new Date());
        clinicDoctorDictMapper.updateByPrimaryKeySelective(dbDict);
    }

    /**
     * 启用医生挂号时间间隔字典
     * @param dict 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void enableDict(ClinicDoctorDict dict) {
        ClinicDoctorDict dbDict = getDictById(dict.getClinicId());
        if (dbDict == null) {
            throw new BusinessFailException("设置不存在");
        }
        if (YesOrNoEnum.YES.getValue().equals(dbDict.getIsEnabled())) {
            return;
        }
        dbDict.setIsEnabled(YesOrNoEnum.YES.getValue());
        dbDict.setUpdateUserId(dict.getUpdateUserId());
        dbDict.setUpdateBy(dict.getUpdateBy());
        dbDict.setUpdateTime(new Date());
        clinicDoctorDictMapper.updateByPrimaryKeySelective(dbDict);
    }

    /**
     * 停用医生挂号时间间隔字典
     * @param dict 实体
     */
    @Transactional(rollbackFor = Exception.class)
    public void disableDict(ClinicDoctorDict dict) {
        ClinicDoctorDict dbDict = getDictById(dict.getClinicId());
        if (dbDict == null) {
            throw new BusinessFailException("设置不存在");
        }
        if (YesOrNoEnum.NO.getValue().equals(dbDict.getIsEnabled())) {
            return;
        }
        dbDict.setIsEnabled(YesOrNoEnum.NO.getValue());
        dbDict.setUpdateUserId(dict.getUpdateUserId());
        dbDict.setUpdateBy(dict.getUpdateBy());
        dbDict.setUpdateTime(new Date());
        clinicDoctorDictMapper.updateByPrimaryKeySelective(dbDict);
    }
}
